UnityEngine.Application.backgroundLoadingPriority = UnityEngine.ThreadPriority.Normal;

MEIJIULE = false

if UnityEngine.Application.platform == UnityEngine.RuntimePlatform.IPhonePlayer then --iphone性能好但是内存敏感，都用这种回收模式
    MIN_ALIVE_INTERVAL = 0.5
    if UnityEngine.SystemInfo.systemMemorySize <= 1024 then
        MEIJIULE = true
    end
else
    MIN_ALIVE_INTERVAL = 2
end

--collectgarbage("setpause", 100)
--collectgarbage("setstepmul",300)

CheckPatterns = {
    "(.*)/prefab/ui/(.*).prefab",
    "(.*)/prefab/hangupmissionmap/(.*).prefab"
}
function CheckResFileExit(res,path)
    if  LoadManager.loadFromEditor or VersionInfo.cppVersion < 20 then
        return
    end
    if not res then
        local fileMiss = false
        for i = 1, #CheckPatterns do
            if  string.find( string.lower(path),CheckPatterns[i]) then
                fileMiss = true
                break
            end
        end
        if fileMiss then

            ShowConfirmBox(false, LanModel.GetTextByID(501827),LanModel.GetTextByID(760000), function(confirm)
                UnityEngine.PlayerPrefs.SetInt('StaticResVersion', 1)
                PlayerPrefs.Save()
                local data = string.format("%s %s %s",path, UnityEngine.SystemInfo.deviceModel,UnityEngine.SystemInfo.deviceName)
                System.IO.File.WriteAllText( System.IO.Path.Combine(Application.persistentDataPath, "file_missing.txt"), data)
                UnityEngine.Application.Quit()
            end)
        end
    end
end

AssetState = {
    Loading = 0,
    Loaded = 1,
    Released = 2,
    Releasing = 3,
}

AssetCache = class('AssetCache')
function AssetCache:ctor(path, name, assetType)
    self.path = path
    self.name = name
    self.assetType = assetType
    self.reference = {}
    self.state = AssetState.Released
    self.beginReleasingTime = 0
    self.bundlePath = nil
    self.request = nil

    self.resource = nil

    self.loadCos = {}
end

function AssetCache:LoadAsync(loadQueue)
    if self.bundlePath == nil then
        error("LoadAsync No bundlePath Find!!!!!!!!!!!!!!")
        return --不要加载没有bundle信息的资源
    end

    self:StartLoad(loadQueue)
    self:WaitForLoadFinishAsync()
    self.request = nil
end

function AssetCache:WaitForLoadFinishAsync()
    coroutine.waitUntil(function()
        return self.state == AssetState.Loaded or self.state == AssetState.Releasing
    end)
end

function AssetCache:StartLoad(loadQueue)
    self.request = RESM.AddLoadJob(self, loadQueue)
    self.state = AssetState.Loading
end

function AssetCache:Load()
    if self.bundlePath == nil then
        error("Load No bundlePath Find!!!!!!!!!!!!!!")
        return --不要加载没有bundle信息的资源
    end

    self.state = AssetState.Loaded
    self.resource = resMgr:LoadAsset(self.path, self.name, self.assetType)
    CheckResFileExit(self.resource,self.path)
end

function AssetCache:OnLoaded(resource)
    self.resource = resource
    self.state = AssetState.Loaded
    CheckResFileExit(self.resource,self.path)
end

function AssetCache:Release()
    if self.state ~= AssetState.Releasing then
        error("Try to Release a unfinished task!!!!!!!!!!!!!!")
        return false --不要卸载正在加载的资源
    end

    if self.bundlePath == nil then
        error("Release No bundlePath Find!!!!!!!!!!!!!!")
        return false --不要卸载没有bundle信息的资源
    end

    self.resource = nil
    self.state = AssetState.Released
    --print('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$', self.path, self.name)
    self:ReleaseBundle()
    return true
end

function AssetCache:ReleaseBundle()
    if LoadManager.loadFromEditor then
        return
    end
    local abname = string.lower(self.bundlePath) .. '.unity3d'
    BundleManager.UnloadAssetBundles(abname)
end

function AssetCache:Require(go)
    table.insert(self.reference, go)
    self.state = AssetState.Loaded
end

function AssetCache:Discard(go)
    table.remove_value(self.reference, go)
end

function AssetCache:UpdateAsset()
    if self.state == AssetState.Loaded then
        for i = #self.reference, 1, -1 do
            local go = self.reference[i]

            if utils.IsAvailableGameObject(go) == false then
                table.remove(self.reference, i)
            end
        end

        if #self.reference == 0 then
            self.beginReleasingTime = os.time()
            self.state = AssetState.Releasing
        end
    elseif self.state == AssetState.Releasing and os.time() - self.beginReleasingTime > MIN_ALIVE_INTERVAL and RESM.refCount <= 0 then
        return self:Release()
    end

    return false
end

module("RESM",package.seeall)

assetDictAsync = {}--异步加载资源
assetDictSync = {}--同步加载资源
bundleDict = {}--bundle的管理

LOAD_WORKER_NUM = 4

local asyncLoadQueue = {}

refCount = 0

local function StartWorker(i)
    coroutine.start(function() --worker
        while true do
            coroutine.waitUntil(function() return #asyncLoadQueue > 0 end)
            local job = asyncLoadQueue[1]
            table.remove(asyncLoadQueue, 1)

            for i = #job.assetCache.loadCos, 1, -1 do
                if coroutine.isCoroutineRunning(job.assetCache.loadCos[i]) == false then
                    table.remove(job.assetCache.loadCos, i)
                end
            end

            if #job.assetCache.loadCos > 0 then
                local request = resMgr:LoadAssetAsync(job.assetCache.path, job.assetCache.name, job.assetCache.assetType)
                coroutine.waitFor(request)
                job.assetCache:OnLoaded(request.resource)
            else
                job.assetCache.state = AssetState.Released --没人要的任务直接认为是release状态
            end
        end
    end)
end

for i = 1, LOAD_WORKER_NUM do
    StartWorker(i)
end

AsyncLoadJob = class('AsyncLoadJob')
function AsyncLoadJob:ctor(assetCache)
    self.assetCache = assetCache
    self.resource = nil
end

function AddLoadJob(assetCache, queue)
    local job = AsyncLoadJob.new(assetCache)
    table.insert(queue, job)
    return job
end

local ResDict = {}
function ShowResDict()
    print('===================================')
    for path, _ in pairs(ResDict) do
        print('@@@', path)
    end
    print('===================================')
end

local function GetAssetPath(path, name)
    if config.AUTO_TEST_GUIDE then
        ResDict[path] = true
    end

    name = name or ""
    local assetPath = path .. '@' .. name
    return assetPath
end

function RedirectPath(path)
    if string.starts_with(string.lower(path), 'res/') then
        local initialResPath = 'ResInitial/' .. string.sub(path,5)
        if resMgr:FileExists(initialResPath) then
            return initialResPath
        else
            if config.AUTO_TEST_GUIDE then
                logError('MISSING RES!!!!!!!!!!!!!!!!!!!!!!!!! ' .. path)
                return path
            else
                return path
            end
        end
    else
        return path
    end
end

function FileExists(path)
    return resMgr:FileExists(path) or resMgr:FileExists(RedirectPath(path))
end

function LoadAssetAsync(path, name, assetType, priority)
    path = RedirectPath(path)
    local assetPath = GetAssetPath(path, name)

    local assetCache = assetDictSync[assetPath]
    if assetCache and (assetCache.state == AssetState.Loaded or assetCache.state == AssetState.Releasing) then
        return assetCache
    end

    local queue = asyncLoadQueue

    assetCache = assetDictAsync[assetPath]
    if assetCache == nil then--之前没有资源，重新加载
        assetCache = AssetCache.new(path, name, assetType)
        assetCache.bundlePath = path
        assetDictAsync[assetPath] = assetCache
        table.insert(assetCache.loadCos, coroutine.running())
        assetCache:LoadAsync(queue)
        table.remove_value(assetCache.loadCos, coroutine.running())
    else
        if assetCache.state == AssetState.Released then
            table.insert(assetCache.loadCos, coroutine.running())
            assetCache:LoadAsync(queue)
            table.remove_value(assetCache.loadCos, coroutine.running())
        elseif assetCache.state == AssetState.Loading then--之前有加载资源，但是没有完成，等待这个任务完成
            table.insert(assetCache.loadCos, coroutine.running())
            assetCache:WaitForLoadFinishAsync()
            table.remove_value(assetCache.loadCos, coroutine.running())
        end
    end

    return assetCache
end

function LoadAsset(path, name, assetType)
    path = RedirectPath(path)
    local assetPath = GetAssetPath(path, name)
    local assetCache = assetDictSync[assetPath]

    if assetCache == nil then--之前没有资源，重新加载
        assetCache = AssetCache.new(path, name, assetType)
        assetCache.bundlePath = path
        assetDictSync[assetPath] = assetCache
        assetCache:Load()
    else
        if assetCache.state == AssetState.Released then
            assetCache:Load()
        end
    end

    return assetCache
end

function InstantiateAssetAsync(path, name, parentTf, priority)
    local asset = LoadAssetAsync(path, name,nil, priority)
    if asset.resource == nil then
        print('Asset is Null!!!!!!!!!!!!!!!!!!', path, name)
    end
    local go = nil
    if parentTf~=nil then
        go = GameObject.Instantiate(asset.resource, parentTf)
    else
        go = GameObject.Instantiate(asset.resource)
    end
    asset:Require(go)

    return go
end

function InstantiateAsset(path, name, parentTf)
    local asset = LoadAsset(path, name)
    local go = nil
    if not asset.resource then
        return nil
    end
    if parentTf~=nil then
        go = GameObject.Instantiate(asset.resource, parentTf)
    else
        go = GameObject.Instantiate(asset.resource)
    end
    asset:Require(go)

    return go
end

function AddRefCount()
    refCount = refCount + 1
end

function SubRefCount()
    refCount = refCount - 1
end

local MAX_ASSET = 20
coroutine.start(function()
    while true do
        if MEIJIULE then
            coroutine.step()
        else
            coroutine.wait(5)
        end

        local count = 0
        for path, asset in pairs(assetDictAsync) do
            asset:UpdateAsset()
            count = count + 1
            if count == MAX_ASSET then
                count = 0
                coroutine.step()
            end
        end
        local newDictAsync = {}
        for path, asset in pairs(assetDictAsync) do
            if asset.state ~= AssetState.Released then
                newDictAsync[path] = asset
            end
        end
        assetDictAsync = newDictAsync

        for path, asset in pairs(assetDictSync) do
            asset:UpdateAsset()
            count = count + 1
            if count == MAX_ASSET then
                count = 0
                coroutine.step()
            end
        end
        local newDictSync = {}
        for path, asset in pairs(assetDictSync) do
            if asset.state ~= AssetState.Released then
                newDictSync[path] = asset
            end
        end
        assetDictSync = newDictSync
    end
end)

function WaitForAllAsyncTaskAsync()
    for path, asset in pairs(assetDictAsync) do
        if asset.state == AssetState.Loading then
            coroutine.waitUntil(function() return asset.state ~= AssetState.Loading end)
        end
    end
end

function Destroy()
    for _, asset in pairs(assetDictAsync) do
        asset:ReleaseBundle()
    end

    for _, asset in pairs(assetDictSync) do
        asset:ReleaseBundle()
    end
end


